/*
DIP

*/

//#include <ip.h>

#include <dip/dip.h>
//#include <dip/dip_neighbor.h>
#include <random.h>
#include <cmu-trace.h>
//#include <energy-model.h>

#define max(a,b)        ( (a) > (b) ? (a) : (b) )
#define CURRENT_TIME    Scheduler::instance().clock()

//#define DEBUG
//#define ERROR

//#ifdef DEBUG
//static int route_request = 0;
//#endif


/*
  TCL Hooks
*/


int hdr_dip_hello::offset_;
static class DIPHeaderClass : public PacketHeaderClass {
public:
        DIPHeaderClass() : PacketHeaderClass("PacketHeader/DIP",
                                              sizeof(hdr_dip_hello)) {
	  bind_offset(&hdr_dip_hello::offset_);
	} 
} class_rtProtoDIP_hdr;

static class DIPclass : public TclClass {
public:
        DIPclass() : TclClass("Agent/DIP") {}
        TclObject* create(int argc, const char*const* argv) {
          assert(argc == 5);
          //return (new DIP((nsaddr_t) atoi(argv[4])));
	  return (new DIP((nsaddr_t) Address::instance().str2addr(argv[4])));
        }
} class_rtProtoDIP;


int
DIP::command(int argc, const char*const* argv) {
  if(argc == 2) {
  Tcl& tcl = Tcl::instance();
    
    if(strncasecmp(argv[1], "id", 2) == 0) {
      tcl.resultf("%d", index);
      return TCL_OK;
    }
    
    if(strncasecmp(argv[1], "start", 2) == 0) {
      btimer.handle((Event*) 0);
htimer.handle((Event*) 0);
      ntimer.handle((Event*) 0);
#ifndef DIP_LINK_LAYER_DETECTION
      htimer.handle((Event*) 0);
      ntimer.handle((Event*) 0);
#endif // LINK LAYER DETECTION

      
      return TCL_OK;
     }               
  }
  else if(argc == 3) {
    if(strcmp(argv[1], "index") == 0) {
      index = atoi(argv[2]);
      return TCL_OK;
    }

    else if(strcmp(argv[1], "log-target") == 0 || strcmp(argv[1], "tracetarget") == 0) {
      logtarget = (Trace*) TclObject::lookup(argv[2]);
      if(logtarget == 0)
	return TCL_ERROR;
      return TCL_OK;
    }
    else if(strcmp(argv[1], "drop-target") == 0) {
    int stat = rqueue.command(argc,argv);
      if (stat != TCL_OK) return stat;
      return Agent::command(argc, argv);
    }
    else if(strcmp(argv[1], "if-queue") == 0) {
    ifqueue = (PriQueue*) TclObject::lookup(argv[2]);
      
      if(ifqueue == 0)
	return TCL_ERROR;
      return TCL_OK;
    }
    else if (strcmp(argv[1], "port-dmux") == 0) {
    	dmux_ = (PortClassifier *)TclObject::lookup(argv[2]);
	if (dmux_ == 0) {
		fprintf (stderr, "%s: %s lookup of %s failed\n", __FILE__,
		argv[1], argv[2]);
		return TCL_ERROR;
	}
	return TCL_OK;
    }
  }
  return Agent::command(argc, argv);
}

/* 
   Constructor
*/

DIP::DIP(nsaddr_t id) : Agent(PT_DIP),
			  btimer(this), htimer(this), ntimer(this), 
			  rqueue() {
 
                
  index = id;
  

  LIST_INIT(&nbhead);
  logtarget = 0;
  ifqueue = 0;
  //printf("%f\n",rqueue.head());
}

/*
  Timers
*/

void
PredicTimer::handle(Event*) {
  agent->predic_update(); //periodically update the delivery predictability
  Scheduler::instance().schedule(this, &intr, PREDIC_INTERVAL);
}

void
DIPHelloTimer::handle(Event*) {
   agent->sendHello();
  //printf("DIPHelloTimer");
   double interval = MinHelloInterval + 
                 ((MaxHelloInterval - MinHelloInterval) * Random::uniform());
   assert(interval >= 0);
   Scheduler::instance().schedule(this, &intr, interval);
}

void
DIPNeighborTimer::handle(Event*) {
  agent->nb_purge();
  //printf("DIPNeighborTimer");
  Scheduler::instance().schedule(this, &intr, HELLO_INTERVAL);
}




/*
  Link Failure Management Functions
*/

static void
dip_rt_failed_callback(Packet *p, void *arg) {
  ((DIP*) arg)->rt_ll_failed(p);
}

/*
 * This routine is invoked when the link-layer reports a route failed.
 */
void
DIP::rt_ll_failed(Packet *p) {
struct hdr_cmn *ch = HDR_CMN(p);
struct hdr_ip *ih = HDR_IP(p);
nsaddr_t broken_nbr = ch->next_hop_;

#ifndef DIP_LINK_LAYER_DETECTION
 drop(p, DROP_RTR_MAC_CALLBACK);
#else 

 /*
  * Non-data packets and Broadcast Packets can be dropped.
  */
  if(! DATA_PACKET(ch->ptype()) ||
     (u_int32_t) ih->daddr() == IP_BROADCAST) {
    drop(p, DROP_RTR_MAC_CALLBACK);
    return;
  }
  log_link_broke(p);
	if(nb_lookup(broken_nbr)==0) {
    drop(p, DROP_RTR_MAC_CALLBACK);
    return;
  }
  log_link_del(ch->next_hop_);
  nb_delete(broken_nbr);
  //printf("rt_ll_failed");

#endif // LINK LAYER DETECTION
}


/*
  Route Handling Functions
*/

void
DIP::rt_resolve(Packet *p) {
struct hdr_cmn *ch = HDR_CMN(p);
struct hdr_ip *ih = HDR_IP(p);
DIP_Neighbor *nb_dst, *nexthop;

//printf("rt_resolve");

 /*
  *  Set the transmit failure callback.  That
  *  won't change.
  */
 ch->xmit_failure_ = dip_rt_failed_callback;
 ch->xmit_failure_data_ = (void*) this;
/*
 * keep the packets
 *
 */
nb_dst = nb_lookup(ih->daddr());
if(ch->ptype() != PT_DIP) {

if(ih->daddr() == here_.addr_) {
forward((nsaddr_t)0,p,NO_DELAY);
}
else if(nb_dst) {
forward(nb_dst->nb_addr,p,NO_DELAY);
 }
else {
   //rqueue.enque(p);
   nexthop=GetUtility(p);
   if(nexthop)
   forward(nexthop->nb_addr,p ,NO_DELAY);
   else  {
    rqueue.enque(p);
    //if(nbhead.lh_first !=0)
//forward(nbhead.lh_first->nb_addr,p ,NO_DELAY);
}
}
 
}
}



/*
  Packet Reception Routines
*/

void
DIP::recv(Packet *p, Handler*) {
struct hdr_cmn *ch = HDR_CMN(p);
struct hdr_ip *ih = HDR_IP(p);
// printf("recv");
 assert(initialized());
 //assert(p->incoming == 0);
 // XXXXX NOTE: use of incoming flag has been depracated; In order to track direction of pkt flow, direction_ in hdr_cmn is used instead. see packet.h for details.

 if(ch->ptype() == PT_DIP) {
   //printf("recvDIP");
   ih->ttl_ -= 1;
   recvDIP(p);
   return;
 }


 /*
  *  Must be a packet I'm originating...
  */
if((ih->saddr() == index) && (ch->num_forwards() == 0)) {
   //printf("a packet I'm originating...");
 /*
  * Add the IP Header.  
  * TCP adds the IP header too, so to avoid setting it twice, we check if
  * this packet is not a TCP or ACK segment.
  */
  if (ch->ptype() != PT_TCP && ch->ptype() != PT_ACK) {
    ch->size() += IP_HDR_LEN;
  }
   // Added by Parag Dadhania && John Novatnack to handle broadcasting
  if ( (u_int32_t)ih->daddr() != IP_BROADCAST) {
    ih->ttl_ = NETWORK_DIAMETER;
  }
}
 
 /*
  *  I received a packet that I sent.  Probably
  *  a routing loop.
  */
else if(ih->saddr() == index) {
   drop(p, DROP_RTR_ROUTE_LOOP);
   return;
 }
 /*
  *  Packet I'm forwarding...
  */
 else {
 /*
  *  Check the TTL.  If it is zero, then discard.
  */
   if(--ih->ttl_ == 0) {
     drop(p, DROP_RTR_TTL);
     return;
   }
 }
// Added by Parag Dadhania && John Novatnack to handle broadcasting
 if ( (u_int32_t)ih->daddr() != IP_BROADCAST)
   {rt_resolve(p);
///printf("recv a packet not a bc");
}
 else
   {forward((nsaddr_t) 0, p, NO_DELAY);
//printf("recv a bc packet ");
}
}


void
DIP::recvDIP(Packet *p) {
 struct hdr_dip_hello *ah = HDR_DIP_HELLO(p);
 //printf("recvDIP");
 assert(HDR_IP (p)->sport() == RT_PORT);
 assert(HDR_IP (p)->dport() == RT_PORT);

 /*
  * Incoming Packets.
  */
if(ah->ah_type==DIPTYPE_HELLO)
   recvHello(p);
else 
{
   fprintf(stderr, "Invalid DIP type (%x)\n", ah->ah_type);
   printf("exit");
   exit(1);
}
 
}





/*
   Packet Transmission Routines
*/

void
DIP::forward(nsaddr_t nexthop, Packet *p, double delay) {
struct hdr_cmn *ch = HDR_CMN(p);
struct hdr_ip *ih = HDR_IP(p);

 if(ih->ttl_ == 0) {

#ifdef DEBUG
  fprintf(stderr, "%s: calling drop()\n", __PRETTY_FUNCTION__);
#endif // DEBUG
 
  drop(p, DROP_RTR_TTL);
  return;
 }

 if ((( ch->ptype() != PT_DIP && ch->direction() == hdr_cmn::UP ) &&
	((u_int32_t)ih->daddr() == IP_BROADCAST))
		|| (ih->daddr() == here_.addr_)) {
//	printf("forward");
        dmux_->recv(p,0);

	return;
 }
if (nexthop) {
   
   ch->next_hop_ = nexthop;
   ch->addr_type() = NS_AF_INET;
   ch->direction() = hdr_cmn::DOWN;       //important: change the packet's direction
 }
 else { // if it is a broadcast packet
   // assert(ch->ptype() == PT_AODV); // maybe a diff pkt type like gaf
   assert(ih->daddr() == (nsaddr_t) IP_BROADCAST);
   ch->addr_type() = NS_AF_NONE;
   ch->direction() = hdr_cmn::DOWN;       //important: change the packet's direction
 }
   
if (ih->daddr() == (nsaddr_t) IP_BROADCAST) {
 // If it is a broadcast packet
   //assert(rt == 0);
   if (ch->ptype() == PT_DIP) {
     /*
      *  Jitter the sending of DIP broadcast packets by 10ms
      */
     Scheduler::instance().schedule(target_, p,
      				   0.01 * Random::uniform());
   } else {
     Scheduler::instance().schedule(target_, p, 0.);  // No jitter
   }
 }
 else { // Not a broadcast packet 
   if(delay > 0.0) {
     Scheduler::instance().schedule(target_, p, delay);
   }
   else {
   // Not a broadcast packet, no delay, send immediately
     Scheduler::instance().schedule(target_, p, 0.);
   }
 }

}


/*
   Neighbor Management Functions
*/

void
DIP::sendHello() {
Packet *p = Packet::alloc();
struct hdr_cmn *ch = HDR_CMN(p);
struct hdr_ip *ih = HDR_IP(p);
struct hdr_dip_hello *rh = HDR_DIP_HELLO(p);
//printf("sendhello");
#ifdef DEBUG
fprintf(stderr, "sending Hello from %d at %.2f\n", index, Scheduler::instance().clock());
#endif // DEBUG

 rh->ah_type = DIPTYPE_HELLO;
 //rh->rp_flags = 0x00;
  
 rh->rp_dst = index;
 //rh->rp_dst_seqno = seqno;
 //rh->rp_lifetime = (1 + ALLOWED_HELLO_LOSS) * HELLO_INTERVAL;

 // ch->uid() = 0;
 ch->ptype() = PT_DIP;
 ch->size() = IP_HDR_LEN + rh->size();
 ch->iface() = -2;
 ch->error() = 0;
 ch->addr_type() = NS_AF_NONE;
 ch->prev_hop_ = index;          // DIP hack

 ih->saddr() = index;
 ih->daddr() = IP_BROADCAST;
 ih->sport() = RT_PORT;
 ih->dport() = RT_PORT;
 ih->ttl_ = 1;

 Scheduler::instance().schedule(target_, p, 0.0);
}


void
DIP::recvHello(Packet *p) {
//struct hdr_ip *ih = HDR_IP(p);
struct hdr_dip_hello *rp = HDR_DIP_HELLO(p);
DIP_Neighbor *nb;
//printf("recvhello");
 nb = nb_lookup(rp->rp_dst);
 if(nb == 0) {
   nb_insert(rp->rp_dst);
   retry();
 }
 else {
   nb->nb_expire = CURRENT_TIME +
                   (1.5 * ALLOWED_HELLO_LOSS * HELLO_INTERVAL);
 }
 
 Packet::free(p);
}

void
DIP::retry() {
     Packet *buffered_pkt= rqueue.head();
     DIP_Neighbor *nexthop;
     for(;buffered_pkt;buffered_pkt=buffered_pkt->next_) {
       nexthop= GetUtility(buffered_pkt);
	if(nexthop) {
         forward(nexthop->nb_addr, buffered_pkt, NO_DELAY);
         rqueue.purgesent(buffered_pkt);
}
       
     }
 
}
void
DIP::nb_insert(nsaddr_t id) {
DIP_Neighbor *nb = new DIP_Neighbor(id);
 //printf("nb_insert");
 assert(nb);
 nb->nb_expire = CURRENT_TIME +
                (1.5 * ALLOWED_HELLO_LOSS * HELLO_INTERVAL);
 nb->nb_predic=PREDIC;
 nb->nb_utility=0;
 //nb->nb_queue();
 LIST_INSERT_HEAD(&nbhead, nb, nb_link);
 
}


DIP_Neighbor*
DIP::nb_lookup(nsaddr_t id) {
DIP_Neighbor *nb = nbhead.lh_first;
 //printf("nb_lookup");
 for(; nb; nb = nb->nb_link.le_next) {
   if(nb->nb_addr == id) break;
 }
 return nb;
}


/*
 * Called when we receive *explicit* notification that a Neighbor
 * is no longer reachable.
 */
void
DIP::nb_delete(nsaddr_t id) {
DIP_Neighbor *nb = nbhead.lh_first;
 //printf("nb_delete");
 log_link_del(id);
 for(; nb; nb = nb->nb_link.le_next) {
   if(nb->nb_addr == id) {
     LIST_REMOVE(nb,nb_link);
     delete nb;
     break;
   }
 }

}


/*
 * Purges all timed-out Neighbor Entries - runs every
 * HELLO_INTERVAL * 1.5 seconds.
 */
void
DIP::nb_purge() {
DIP_Neighbor *nb = nbhead.lh_first;
DIP_Neighbor *nbn;
double now = CURRENT_TIME;

 for(; nb; nb = nbn) {
   nbn = nb->nb_link.le_next;
   if(nb->nb_expire <= now) {
     nb_delete(nb->nb_addr);
   }
 }
//printf("nb purge");
}

/*
 *periodically update delivery the predictability
 */
void
DIP::predic_update() {
DIP_Neighbor *nb = nbhead.lh_first;
double now = CURRENT_TIME;
//printf("predic");
for(; nb; nb = nb->nb_link.le_next) {
if(nb->nb_expire-now>(ALLOWED_HELLO_LOSS * HELLO_INTERVAL))
   nb->nb_predic=nb->nb_predic+(1-nb->nb_predic)*PREDIC;
else
   nb->nb_predic=nb->nb_predic*alpha;
   
 }

}
/*
 *get distance by id address
 */
double
DIP::GetDisbyID(nsaddr_t a,nsaddr_t b) {
MobileNode *node_a,*node_b; 
//printf("GetDisbyID");
double distance;
node_a=GetNode(a);
node_b=GetNode(b);
distance=node_a->distance(node_b);
return distance;
}
MobileNode*     
DIP::GetNode(nsaddr_t id) {
double x = 0.0, y = 0.0, z = 0.0; 
MobileNode *thisnode; 
//printf("GetNode");
thisnode = MobileNode::get_node_by_address(id);
//thisnode->getLoc(&x, &y, &z);
return thisnode;
}

DIP_Neighbor*
DIP::GetUtility(Packet *p) {
struct hdr_ip *ih = HDR_IP(p);
DIP_Neighbor *max_nb, *nb = nbhead.lh_first;
double dist_a,dist_b,max_uty=0.0,tmp=0.0,dist;
//int cnt;
nsaddr_t p_dest,nb_addr;
//rqueue.purge();
max_nb=0;
p_dest = ih->daddr();
for(; nb; nb = nb->nb_link.le_next) {
nb_addr=nb->nb_addr;
dist=0.0;
//cnt=0;
   
   dist_a=GetDisbyID(p_dest,index);
   dist_b=GetDisbyID(p_dest,nb_addr);
   if(dist_a>dist_b) {

     dist=dist_a-dist_b;
 //    nb->nb_queue.enque(p);

             }
         

if(dist>0) {

nb->nb_utility=dist;
tmp =(nb->nb_utility)*(nb->nb_predic);

if(tmp>max_uty) {
   max_uty=tmp;
   max_nb=nb;
}
 }
}
//printf("hello\n");
return max_nb;
}

